package com.autumn.platform.dao.interceptor;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.RowBounds;
import org.springframework.jdbc.support.JdbcUtils;

import com.autumn.platform.core.Utils;
import com.autumn.platform.dao.dialect.Dialects;
import com.autumn.platform.dao.dialect.IDialect;
import com.autumn.platform.dao.page.PageableRowBounds;

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class StatementHandlerInterceptor extends AbstractInterceptor {

    private static Field delegate;
    static {
        try {
            delegate = RoutingStatementHandler.class.getDeclaredField("delegate");
            delegate.setAccessible(true);
        } catch (Exception e) {
        }
    }

    /**
     * 执行拦截器逻辑：
     * <p>
     * <ul>
     * <li>1.格式化字符串，去掉首尾空白字符，压缩中间连续多个空白字符为一个空白字符
     * <li>2.如果是分页查询，根据需要计算总记录数，并替换为查询区间段的记录的SQL语句，实现物理分页
     * </ul>
     * <p>
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = super.getTarget(invocation, StatementHandler.class);
        if (handler instanceof RoutingStatementHandler) {
            handler = (StatementHandler) delegate.get(handler);
        }
        MetaObject meta = SystemMetaObject.forObject(handler);

        //拦截SQL：SQL执行参数替换,SQL语句静态替换
        BoundSql boundSql = handler.getBoundSql();
        MappedStatement mappedStatement = (MappedStatement) meta.getValue("mappedStatement");
        ParameterHandler ph = handler.getParameterHandler();

        String sql = boundSql.getSql();
        sql = Utils.formatWhitespace(sql);//格式化SQL字符串
        meta.setValue("boundSql.sql", sql);

        // 分页
        RowBounds bounds = (RowBounds) meta.getValue("rowBounds");
        if (bounds instanceof PageableRowBounds) {
            PageableRowBounds pa = (PageableRowBounds) bounds;
            Connection conn = super.getArgument(invocation, Connection.class, 0);
            IDialect dialect = Dialects.getDialect(conn);//数据库方言

            if (pa.isPaged()) {
                setTotal(pa, mappedStatement.getStatementLog(), conn, ph, dialect, sql);
            }

            //分页或排序SQL
            String pageSql = dialect.getPageableSql(sql, pa.getPageable());
            meta.setValue("rowBounds", RowBounds.DEFAULT);//替换之前的指标
            meta.setValue("boundSql.sql", pageSql);
        }
        return invocation.proceed();
    }

    /**
     * 设置分页属性
     * 
     * @param page 分页对象
     * @param log 日志对象
     * @param conn 数据库
     * @param handler 参数处理器
     * @param dialect 数据库方言
     * @param sql 原始SQL
     */
    private void setTotal(PageableRowBounds page, Log log, Connection conn, ParameterHandler handler, IDialect dialect, String sql) throws SQLException {
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            long total = page.getTotal();
            if (total < 0) {
                pstmt = conn.prepareStatement(dialect.getTotalSql(sql));
                handler.setParameters(pstmt);
                rs = pstmt.executeQuery();
                if (rs.next()) {
                    total = rs.getLong(1);
                }
                if (null != log) {
                    log.debug("<==      Total:(Page) calculate page totals [" + total + "]");
                }
            } else if (null != log) {
                log.debug("<==      Total:(Page) use old page totals [" + total + "]");
            }
            page.setTotal(total);
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(pstmt);
        }
    }
}
